﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System.Collections.Generic;
using Tesla.Bounding;
using Tesla.Graphics;
using Tesla.Math;

namespace Tesla.Scene {
    /// <summary>
    /// IComparer for lights that computes the how much a light affects a given Spatial.
    /// </summary>
    public sealed class LightComparer : IComparer<Light> {
        private Spatial _target;

        /// <summary>
        /// Sets the spatial used during the comparison
        /// </summary>
        /// <param name="target">Spatial to use</param>
        public void SetSpatial(Spatial target) {
            _target = target;
        }

        /// <summary>
        /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
        /// </summary>
        /// <param name="x">The first object to compare.</param>
        /// <param name="y">The second object to compare.</param>
        /// <returns>
        /// Value
        /// Condition
        /// Less than zero
        /// <paramref name="x"/> is less than <paramref name="y"/>.
        /// Zero
        /// <paramref name="x"/> equals <paramref name="y"/>.
        /// Greater than zero
        /// <paramref name="x"/> is greater than <paramref name="y"/>.
        /// </returns>
        public int Compare(Light x, Light y) {
            float first = GetLightValue(x, _target.WorldBounding);
            float second = GetLightValue(y, _target.WorldBounding);
            float compare = first - second;
            if(compare < 0) {
                return 1;
            } else if(compare > 0) {
                return -1;
            } else {
                return 0;
            }
        }

        private float GetLightValue(Light light, BoundingVolume volume) {
            if(!light.IsEnabled) {
                return 0;
            } else {
                switch(light.LightType) {
                    case LightType.Directional:
                        return GetColorStrength(light);
                    case LightType.Point:
                        return GetPointValue(light as PointLight, volume);
                    case LightType.Spot:
                        return GetSpotValue(light as SpotLight, volume);
                    case LightType.Custom:
                    default:
                        return .5f;
                }
            }
        }

        private float GetPointValue(PointLight light, BoundingVolume volume) {
            if(volume == null) {
                return 0;
            }

            if(!light.Attenuate) {
                return GetColorStrength(light);
            } else {
                Vector3 pos = light.Position;
                float dist = volume.DistanceTo(pos);
                float strength = GetColorStrength(light);
                float att = light.Constant + (light.Linear * dist) + (light.Quadratic * dist * dist);
                return strength / att;
            }
        }

        private float GetSpotValue(SpotLight light, BoundingVolume volume) {
            if(volume == null) {
                return 0;
            }

            Vector3 dir = light.Direction;
            Vector3 pos = light.Position;
            Plane plane = new Plane(dir, Vector3.Dot(dir, pos));
            PlaneIntersectionType result;
            volume.Intersects(ref plane, out result);
            if(result != PlaneIntersectionType.Back) {
                if(!light.Attenuate) {
                    return GetColorStrength(light);
                } else {
                    float dist = volume.DistanceTo(pos);
                    float strength = GetColorStrength(light);
                    float att = light.Constant + (light.Linear * dist) + (light.Quadratic * dist * dist);
                    return strength / att;
                }
            } else {
                return 0;
            }
        }

        private float GetColorStrength(Light light) {
            return Strength(light.Ambient) + Strength(light.Diffuse);
        }

        private float Strength(Color c) {
            float r = c.R;
            float g = c.G;
            float b = c.B;
            return MathHelper.Sqrt(r*r + g*g + b*b);
        }
    }
}
